class_name FlowFieldPathFinding

enum DirectionType
{
	Four,
	Eight,
}
#region 属性
# 这个tilemaplayer是用来设置cell的大小和整个坐标系位置的，可以是已有的tilemaplayer，也可以是单独创建的空tilemaplayer，其它的tilemaplayer的cell大小目前必须和base的一样大
# TODO 未来需要支持是base的cell大小整数倍大小的tilemaplayer
var base_tilemap_layer:TileMapLayer
var direction_type:DirectionType = DirectionType.Four
var grid_node_dict:Dictionary = {}
# 用于标识同一轮Target更新
var update_id:int
var cell_size:Vector2:
	get:
		if base_tilemap_layer:
			return base_tilemap_layer.tile_set.tile_size
		return Vector2.ZERO
var target_pos:Vector2 = Vector2(INF,INF)
#endregion
#region 字段
#endregion
#region 信号
#endregion

#region 基类方法
#endregion
#region 公共方法
func reset():
	for node:FlowFieldPathFinding_GridNode in grid_node_dict.values():
		node.reset()
func init_grid_node_dict_by_tilemap_layer(layer:TileMapLayer,is_walkable:bool, cost:float = 1):
	if base_tilemap_layer:
		for pos in layer.get_used_cells():
			var global_pos = layer.to_global(layer.map_to_local(pos))
			pos = base_tilemap_layer.local_to_map(base_tilemap_layer.to_local(global_pos))
			if grid_node_dict.has(pos):
				var grid_node:FlowFieldPathFinding_GridNode = grid_node_dict[pos]
				grid_node.cost = cost
				grid_node.is_walkable = is_walkable
			else:
				var grid_node = FlowFieldPathFinding_GridNode.new(pos.x,pos.y,is_walkable,cost)
				grid_node_dict[pos] = grid_node
	else:
		for pos in layer.get_used_cells():
			if grid_node_dict.has(pos):
				var grid_node:FlowFieldPathFinding_GridNode = grid_node_dict[pos]
				grid_node.cost = cost
				grid_node.is_walkable = is_walkable
			else:
				var grid_node = FlowFieldPathFinding_GridNode.new(pos.x,pos.y,is_walkable,cost)
				grid_node_dict[pos] = grid_node
	reset()
func set_target(pos:Vector2) -> bool:
	var real_pos:Vector2i = base_tilemap_layer.local_to_map(base_tilemap_layer.to_local(pos))
	if not grid_node_dict.has(real_pos) or grid_node_dict[real_pos].is_walkable == false:
		return false
	update_id += 1
	self.target_pos = pos
	reset()
	var grid_node:FlowFieldPathFinding_GridNode = grid_node_dict[real_pos]
	grid_node.final_cost = 0
	grid_node.dir = Vector2.ZERO
	var queue:Array[Vector2i] = []
	queue.push_back(real_pos)
	while queue.size() > 0:
		real_pos = queue.pop_front()
		var cur_node:FlowFieldPathFinding_GridNode = grid_node_dict[real_pos]
		cur_node.update_id = update_id
		match direction_type:
			DirectionType.Four:
				var closed_node:FlowFieldPathFinding_GridNode
				for offest:Vector2i in Constant.FOUR_DIR_OFFEST:
					var target_pos = real_pos + offest
					if grid_node_dict.has(target_pos):
						var target_node:FlowFieldPathFinding_GridNode = grid_node_dict[target_pos]
						if target_node.is_walkable:
							if target_node.update_id < update_id:
								var cost = cur_node.final_cost + target_node.cost * offest.length()
								target_node.final_cost = cost if cost < target_node.final_cost else target_node.final_cost
								queue.push_back(target_pos)
							if cur_node != grid_node:
								if not closed_node:
									closed_node = target_node
									cur_node.dir = Vector2(offest).normalized()
									cur_node.target_grid_node = closed_node
								elif closed_node.final_cost > target_node.final_cost:
									closed_node = target_node
									cur_node.dir = Vector2(offest).normalized()
									cur_node.target_grid_node = closed_node
								elif closed_node.final_cost == target_node.final_cost and grid_node.get_pos_vec().distance_squared_to(closed_node.get_pos_vec()) > grid_node.get_pos_vec().distance_squared_to(target_pos):
									closed_node = target_node
									cur_node.dir = Vector2(offest).normalized()
									cur_node.target_grid_node = closed_node
						else:
							target_node.update_id = update_id
							target_node.dir = Vector2.ZERO
							target_node.target_grid_node = null
				if not closed_node and cur_node != grid_node:
					cur_node.dir = Vector2.ZERO
					cur_node.target_grid_node = null
			DirectionType.Eight:
				var closed_node:FlowFieldPathFinding_GridNode
				for offest:Vector2i in Constant.Eight_DIR_OFFEST:
					var target_pos = real_pos + offest
					if grid_node_dict.has(target_pos):
						var target_node:FlowFieldPathFinding_GridNode = grid_node_dict[target_pos]
						if target_node.is_walkable:
							# 去除对角线无法行走的情况
							if offest in Constant.DIAGONAL_DIR_OFFEST:
								var p = real_pos + Vector2i(offest.x,0)
								if not grid_node_dict.has(p):
									continue
								elif not grid_node_dict[p].is_walkable:
									if target_node.update_id < update_id:
										queue.push_back(target_pos)
									continue
								p = real_pos + Vector2i(0,offest.y)
								if not grid_node_dict.has(p):
									continue
								elif not grid_node_dict[p].is_walkable:
									if target_node.update_id < update_id:
										queue.push_back(target_pos)
									continue
							if target_node.update_id < update_id:
								var cost = cur_node.final_cost + target_node.cost * offest.length()
								target_node.final_cost = cost if cost < target_node.final_cost else target_node.final_cost
								queue.push_back(target_pos)
							if cur_node != grid_node:
								if not closed_node:
									closed_node = target_node
									cur_node.dir = Vector2(offest).normalized()
									cur_node.target_grid_node = closed_node
								elif closed_node.final_cost > target_node.final_cost:
									closed_node = target_node
									cur_node.dir = Vector2(offest).normalized()
									cur_node.target_grid_node = closed_node
								elif closed_node.final_cost == target_node.final_cost and grid_node.get_pos_vec().distance_squared_to(closed_node.get_pos_vec()) > grid_node.get_pos_vec().distance_squared_to(target_pos):
									closed_node = target_node
									cur_node.dir = Vector2(offest).normalized()
									cur_node.target_grid_node = closed_node
						else:
							target_node.update_id = update_id
							target_node.dir = Vector2.ZERO
							target_node.target_grid_node = null
				if not closed_node and cur_node != grid_node:
					cur_node.dir = Vector2.ZERO
					cur_node.target_grid_node = null
			_:
				pass
	return true
func get_target_direction(pos:Vector2) -> Vector2:
	var real_pos:Vector2i = base_tilemap_layer.local_to_map(base_tilemap_layer.to_local(pos))
	if grid_node_dict[real_pos]:
		var grid_node:FlowFieldPathFinding_GridNode = grid_node_dict[real_pos]
		if grid_node.update_id == update_id:
			return grid_node.dir
	return Vector2.ZERO
func get_path(pos:Vector2) -> Array[Vector2]:
	var real_pos:Vector2i = base_tilemap_layer.local_to_map(base_tilemap_layer.to_local(pos))
	var path:Array[Vector2] = []
	if grid_node_dict.has(real_pos):
		var grid_node:FlowFieldPathFinding_GridNode = grid_node_dict[real_pos]
		var global_pos:Vector2 = base_tilemap_layer.to_global(base_tilemap_layer.map_to_local(grid_node.get_pos_vec()))
		path.append(global_pos)
		while grid_node.target_grid_node:
			global_pos = base_tilemap_layer.to_global(base_tilemap_layer.map_to_local(grid_node.target_grid_node.get_pos_vec()))
			path.append(global_pos)
			grid_node = grid_node.target_grid_node
	return path
func draw_map(draw_node:Node):
	for node:FlowFieldPathFinding_GridNode in grid_node_dict.values():
		draw_node.draw_rect(Rect2(node.get_pos_vec() * cell_size.x,cell_size),Color.GRAY,false,1.5)
		if node.target_grid_node:
			var vec = Vector2(node.get_pos_vec()) + (Vector2(node.target_grid_node.get_pos_vec()) - Vector2(node.get_pos_vec())).normalized() * 0.4
			draw_node.draw_line(node.get_pos_vec() * cell_size.x + cell_size/2,vec * cell_size.x + cell_size/2,Color.GREEN,2)
			draw_node.draw_circle(node.get_pos_vec() * cell_size.x + cell_size/2,1,Color.BLUE)
	if target_pos != Vector2(INF,INF):
		draw_node.draw_circle(target_pos,4,Color.BLACK)
	draw_node.queue_redraw()
func draw_path(draw_node:Node,start_pos:Vector2,path:Array[Vector2]):
	draw_node.draw_circle(start_pos,4,Color.WHITE)
	for point in path:
		draw_node.draw_circle(point,2,Color.RED)
	draw_node.queue_redraw()
#endregion
#region 私有方法
#endregion
#region 生命周期函数
func _init(base_tilemap_layer:TileMapLayer,cost:float = 1,direction_type:DirectionType = DirectionType.Four) -> void:
	self.base_tilemap_layer = base_tilemap_layer
	self.direction_type = direction_type
	self.grid_node_dict = {}
	self.update_id = -1
	init_grid_node_dict_by_tilemap_layer(self.base_tilemap_layer,true,cost)
#endregion
